package com.mazaiting.redisson.aspect;

import com.mazaiting.redisson.aspect.annos.RedisLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * redis 分布式锁切面
 *
 * @author mazaiting
 * @create_time 2022/6/6 12:36
 */
@Aspect
public class RedisLockAspect {
    /**
     * 参数名发现类
     */
    private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Resource
    private RedissonClient redissonClient;

    @Around(value = "@annotation(redisLock)")
    public Object lock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        // 拼接出key 使用spel解析注解中定义的key
        String key = getRedisKey(joinPoint, redisLock);
        // 获取锁
        RLock lock = redissonClient.getLock(key);
        // 加锁结果
        boolean lockResult = false;
        try {
            // 加锁
            lockResult = lock.tryLock(redisLock.waitTime(), redisLock.releaseTime(), redisLock.unit());
            if (lockResult) {
                return joinPoint.proceed();
            }
        } finally {
            // 判断是否是当前线程
            if (lockResult && lock.isHeldByCurrentThread()) {
                lock.forceUnlock();
            }
        }
        return null;
    }

    /**
     * 获取 Redis key
     *
     * @param joinPoint 切入点
     * @param redisLock redis 锁注解
     * @return key
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
        // 表达式上下文
        EvaluationContext context = new MethodBasedEvaluationContext(TypedValue.NULL, resolveMethod(joinPoint), joinPoint.getArgs(), parameterNameDiscoverer);
        StringBuilder sb = new StringBuilder();
        // 判断前缀是否为空
        String prefix = redisLock.prefix();
        if (StringUtils.hasLength(prefix)) {
            sb.append(prefix);
        }
        // 表达式解析器
        ExpressionParser parser = new SpelExpressionParser();
        for (String key : redisLock.keys()) {
            // keys是个spel表达式
            Expression expression = parser.parseExpression(key);
            Object value = expression.getValue(context);
            sb.append(ObjectUtils.nullSafeToString(value));
        }
        return redisLock.prefix() + sb;
    }

    /**
     * 加载方法
     *
     * @param joinPoint 切入点
     * @return 方法
     */
    private Method resolveMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> targetClass = joinPoint.getTarget().getClass();

        return getDeclaredMethodFor(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
    }

    /**
     * 定义方法
     *
     * @param clazz          类实例
     * @param name           名字
     * @param parameterTypes 参数类型
     * @return 方法
     */
    private Method getDeclaredMethodFor(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethodFor(superClass, name, parameterTypes);
            }
        }
        throw new IllegalStateException("Cannot resolve target method: " + name);
    }
}
